﻿#include "lua_util.hh"
#include "../../http/http_call.hh"
#include "../../time/local_system_time.hh"
#include "../string_util.hh"
#include "lua_helper.hh"

#include <google/protobuf/compiler/importer.h>
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/message.h>
#include <google/protobuf/reflection.h>

#ifdef GetMessage
#undef GetMessage
#endif

namespace kratos {
namespace lua {

namespace LuaUtil {

void pbmsg_to_lua_table(lua_State *L, const ProtobufMessage &msg) {
  const auto *descriptor = msg.GetDescriptor();
  const auto *reflection = msg.GetReflection();
  // 建立一个新的Lua表并压栈
  lua_newtable(L);
  // 遍历msg字段
  for (int i = 0; i < descriptor->field_count(); ++i) {
    const auto *field = descriptor->field(i);
    if (!field->is_repeated() && !reflection->HasField(msg, field)) {
      continue;
    } else {
      // For existing field
      on_field(L, msg, field);
    }
  }
}

bool lua_table_to_pbmsg(lua_State *L, ProtobufMessage &msg) {
  if (!lua_istable(L, -1)) {
    return false;
  }
  const auto *descriptor = msg.GetDescriptor();
  const auto *reflection = msg.GetReflection();
  // 遍历Lua表
  lua_pushnil(L);
  while (lua_next(L, -2)) {
    if (!lua_isstring(L, -2)) {
      lua_pop(L, 1);
      continue;
    }
    const char *key = lua_tostring(L, -2);
    const auto *field = descriptor->FindFieldByName(key);
    if (field) {
      on_lua_table_value(L, msg, reflection, field, -1);
    }
    lua_pop(L, 1);
  }
  return true;
}

void on_repeated_field(lua_State *L, const ProtobufMessage &msg,
                       const ProtobufFieldDescriptor *field) {
  auto count = msg.GetReflection()->FieldSize(msg, field);
  if (field->type() == ProtobufFieldDescriptor::Type::TYPE_MESSAGE) {
    for (auto i = 0; i < count; i++) {
      const auto &repeated_message =
          msg.GetReflection()->GetRepeatedMessage(msg, field, i);
      pbmsg_to_lua_table(L, repeated_message);
    }
  } else {
    for (int i = 0, j = 1; i < count; i++, j++) {
      switch (field->type()) {
      case ProtobufFieldDescriptor::Type::TYPE_BOOL: {
        auto value = msg.GetReflection()->GetRepeatedBool(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushboolean(L, value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_INT32: {
        auto value = msg.GetReflection()->GetRepeatedInt32(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushinteger(L, (lua_Integer)value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_INT64: {
        auto value = msg.GetReflection()->GetRepeatedInt64(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushinteger(L, (lua_Integer)value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_UINT32: {
        auto value = msg.GetReflection()->GetRepeatedUInt32(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushinteger(L, (lua_Integer)value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_UINT64: {
        auto value = msg.GetReflection()->GetRepeatedUInt64(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushinteger(L, (lua_Integer)value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_FLOAT: {
        auto value = msg.GetReflection()->GetRepeatedFloat(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushnumber(L, (lua_Number)value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_DOUBLE: {
        auto value = msg.GetReflection()->GetRepeatedDouble(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushnumber(L, (lua_Number)value);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_ENUM: {
        auto value = msg.GetReflection()->GetRepeatedEnum(msg, field, i);
        auto num = value->number();
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushnumber(L, (lua_Number)num);
        lua_settable(L, -3);
        break;
      }
      case ProtobufFieldDescriptor::Type::TYPE_STRING: {
        auto value = msg.GetReflection()->GetRepeatedString(msg, field, i);
        lua_pushinteger(L, (lua_Integer)j);
        lua_pushstring(L, value.c_str());
        lua_settable(L, -3);
        break;
      }
      default:
        break;
      }
    }
  }
}

void on_map_field_key(lua_State *L, const ProtobufMessage &msg,
                      const ProtobufFieldDescriptor *field) {
  auto key_type = field->type();
  switch (key_type) {
  case ProtobufFieldDescriptor::Type::TYPE_INT32: {
    auto key = msg.GetReflection()->GetInt32(msg, field);
    lua_push(L, key);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_INT64: {
    auto key = msg.GetReflection()->GetInt64(msg, field);
    lua_push(L, key);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_STRING: {
    auto key = msg.GetReflection()->GetString(msg, field);
    lua_push(L, key);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_UINT32: {
    auto key = msg.GetReflection()->GetUInt32(msg, field);
    lua_push(L, key);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_UINT64: {
    auto key = msg.GetReflection()->GetUInt64(msg, field);
    lua_push(L, key);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_ENUM: {
    auto key = msg.GetReflection()->GetEnumValue(msg, field);
    lua_push(L, key);
    break;
  }
  default:
    break;
  }
}

void on_map_field_value(lua_State *L, const ProtobufMessage &msg,
                        const ProtobufFieldDescriptor *field) {
  auto value_type = field->type();
  switch (value_type) {
  case ProtobufFieldDescriptor::Type::TYPE_INT32: {
    auto value = msg.GetReflection()->GetInt32(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_INT64: {
    auto value = msg.GetReflection()->GetInt64(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_STRING: {
    auto value = msg.GetReflection()->GetString(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_UINT32: {
    auto value = msg.GetReflection()->GetUInt32(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_UINT64: {
    auto value = msg.GetReflection()->GetUInt64(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_BOOL: {
    auto value = msg.GetReflection()->GetBool(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_FLOAT: {
    auto value = msg.GetReflection()->GetFloat(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_DOUBLE: {
    auto value = msg.GetReflection()->GetDouble(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_ENUM: {
    auto value = msg.GetReflection()->GetEnumValue(msg, field);
    lua_push(L, value);
    break;
  }
  case ProtobufFieldDescriptor::Type::TYPE_MESSAGE: {
    auto &curMsg = msg.GetReflection()->GetMessage(msg, field);
    pbmsg_to_lua_table(L, curMsg);
    break;
  }
  default:
    break;
  }
}

void on_map_field(lua_State *L, const ProtobufMessage &msg,
                  const ProtobufFieldDescriptor *field) {
  auto count = msg.GetReflection()->FieldSize(msg, field);
  for (int i = 0; i < count; i++) {
    const auto &message_map =
        msg.GetReflection()->GetRepeatedMessage(msg, field, i);
    const auto *descriptor = message_map.GetDescriptor();
    const auto *key_field_descriptor = descriptor->field(0);
    const auto *value_field_descriptor = descriptor->field(1);
    on_map_field_key(L, message_map, key_field_descriptor);
    on_map_field_value(L, message_map, value_field_descriptor);
    lua_settable(L, -3);
  }
}

void on_field(lua_State *L, const ProtobufMessage &msg,
              const ProtobufFieldDescriptor *field) {
  if (field->is_repeated()) {
    lua_newtable(L);
    if (field->is_map()) {
      on_map_field(L, msg, field);
    } else {
      on_repeated_field(L, msg, field);
    }
    lua_setfield(L, -2, field->name().c_str());
  } else {
    switch (field->type()) {
    case ProtobufFieldDescriptor::Type::TYPE_BOOL:
      push_table_bool(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT32:
      push_table_int32(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT64:
      push_table_int64(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT32:
      push_table_uint32(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT64:
      push_table_uint64(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_FLOAT:
      push_table_float(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_DOUBLE:
      push_table_double(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_STRING:
      push_table_string(L, msg, field);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_MESSAGE: {
      const auto &new_message = msg.GetReflection()->GetMessage(msg, field);
      pbmsg_to_lua_table(L, new_message);
      lua_setfield(L, -2, field->name().c_str());
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_ENUM:
      push_table_enum(L, msg, field);
      break;
    default:
      break;
    }
  }
}

bool on_lua_repeated_value(lua_State *L, ProtobufMessage &msg,
                           const ProtobufReflection *ref,
                           const ProtobufFieldDescriptor *field, int index) {
  if (!lua_istable(L, -1)) {
    return false;
  }
  lua_pushnil(L);
  while (lua_next(L, -2)) {
    switch (field->type()) {
    case ProtobufFieldDescriptor::Type::TYPE_BOOL:
      add_field_bool(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT32:
      add_field_int32(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT64:
      add_field_int64(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT32:
      add_field_uint32(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT64:
      add_field_uint64(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_FLOAT:
      add_field_float(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_DOUBLE:
      add_field_double(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_STRING:
      add_field_string(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_MESSAGE: {
      auto *new_message = ref->AddMessage(&msg, field);
      lua_table_to_pbmsg(L, *new_message);
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_ENUM: {
      add_field_enum(L, msg, ref, field, index);
      break;
    }
    default:
      break;
    }
    lua_pop(L, 1);
  }
  return true;
}

bool on_lua_table_value(lua_State *L, ProtobufMessage &msg,
                        const ProtobufReflection *ref,
                        const ProtobufFieldDescriptor *field, int index) {
  if (field->is_repeated()) {
    if (field->is_map()) {
      on_lua_map(L, msg, ref, field, index);
    } else {
      on_lua_repeated_value(L, msg, ref, field, index);
    }
  } else {
    switch (field->type()) {
    case ProtobufFieldDescriptor::Type::TYPE_BOOL:
      set_field_bool(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT32:
      set_field_int32(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT64:
      set_field_int64(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT32:
      set_field_uint32(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT64:
      set_field_uint64(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_FLOAT:
      set_field_float(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_DOUBLE:
      set_field_double(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_STRING:
      set_field_string(L, msg, ref, field, index);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_MESSAGE: {
      lua_table_to_pbmsg(L, *ref->MutableMessage(&msg, field));
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_ENUM: {
      set_field_enum(L, msg, ref, field, index);
      break;
    }
    default:
      break;
    }
  }
  return true;
}

bool on_lua_map(lua_State *L, ProtobufMessage &msg,
                const ProtobufReflection *ref,
                const ProtobufFieldDescriptor *field, int index) {
  if (!lua_istable(L, -1)) {
    return false;
  }
  const auto *descriptor = msg.GetDescriptor();
  const auto *reflection = msg.GetReflection();
  // 遍历Lua表
  lua_pushnil(L);
  while (lua_next(L, -2)) {
    auto *pair_message = reflection->AddMessage(&msg, field);
    auto *pair_reflection = pair_message->GetReflection();
    const auto *key_descriptor = field->message_type()->field(0);
    const auto *value_descriptor = field->message_type()->field(1);
    auto key_type = key_descriptor->type();
    switch (key_type) {
    case ProtobufFieldDescriptor::Type::TYPE_INT32: {
      pair_reflection->SetInt32(pair_message, key_descriptor,
                                (std::int32_t)lua_tointeger(L, -2));
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_INT64: {
      pair_reflection->SetInt64(pair_message, key_descriptor,
                                (std::int64_t)lua_tointeger(L, -2));
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_STRING: {
      pair_reflection->SetString(pair_message, key_descriptor,
                                 lua_tostring(L, -2));
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_UINT32: {
      pair_reflection->SetUInt32(pair_message, key_descriptor,
                                 (std::uint32_t)lua_tointeger(L, -2));
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_UINT64: {
      pair_reflection->SetUInt64(pair_message, key_descriptor,
                                 (std::uint64_t)lua_tointeger(L, -2));
      break;
    }
    default:
      break;
    }
    auto value_type = value_descriptor->type();
    switch (value_type) {
    case ProtobufFieldDescriptor::Type::TYPE_BOOL:
      pair_reflection->SetBool(pair_message, value_descriptor,
                               lua_toboolean(L, -1) ? true : false);
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT32:
      pair_reflection->SetInt32(pair_message, value_descriptor,
                                (std::int32_t)lua_tointeger(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_INT64:
      pair_reflection->SetInt64(pair_message, value_descriptor,
                                (std::int64_t)lua_tointeger(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT32:
      pair_reflection->SetUInt32(pair_message, value_descriptor,
                                 (std::uint32_t)lua_tointeger(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_UINT64:
      pair_reflection->SetUInt64(pair_message, value_descriptor,
                                 (std::uint64_t)lua_tointeger(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_FLOAT:
      pair_reflection->SetFloat(pair_message, value_descriptor,
                                (float)lua_tonumber(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_DOUBLE:
      pair_reflection->SetDouble(pair_message, value_descriptor,
                                 (double)lua_tonumber(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_STRING:
      pair_reflection->SetString(pair_message, value_descriptor,
                                 lua_tostring(L, -1));
      break;
    case ProtobufFieldDescriptor::Type::TYPE_MESSAGE: {
      auto *new_message =
          pair_reflection->MutableMessage(pair_message, value_descriptor);
      lua_table_to_pbmsg(L, *new_message);
      break;
    }
    case ProtobufFieldDescriptor::Type::TYPE_ENUM:
      pair_reflection->SetEnumValue(pair_message, value_descriptor,
                                    (int)lua_tointeger(L, -1));
      break;
    default:
      break;
    }
    lua_pop(L, 1);
  }
  return true;
}

void on_lua_string_map(
    lua_State *L,
    kratos::service::PoolUnorederedMap<std::string, std::string> map) {
  // 遍历Lua表
  lua_pushnil(L);
  while (lua_next(L, -2)) {
    if (!lua_isstring(L, -1) || !lua_isstring(L, -2)) {
      throw std::runtime_error("Only support string table");
    }
    auto *key = lua_tostring(L, -2);
    auto *value = lua_tostring(L, -1);
    map[key] = value;
    lua_pop(L, 1);
  }
}

void on_lua_string_map(lua_State *L,
                       std::unordered_map<std::string, std::string> &map) {
  // 遍历Lua表
  lua_pushnil(L);
  while (lua_next(L, -2)) {
    if (!lua_isstring(L, -1) || !lua_isstring(L, -2)) {
      throw std::runtime_error("Only support string table");
    }
    auto *key = lua_tostring(L, -2);
    auto *value = lua_tostring(L, -1);
    map[key] = value;
    lua_pop(L, 1);
  }
}

void lua_push(lua_State *state, std::int16_t arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}

void lua_push(lua_State *state, std::uint16_t arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}

void lua_push(lua_State *state, std::int32_t arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}

#ifdef __linux__
extern void lua_push(lua_State *state, long long int arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}
#endif // __linux__

void lua_push(lua_State *state, std::int64_t arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}

void lua_push(lua_State *state, std::uint32_t arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}

void lua_push(lua_State *state, std::uint64_t arg) {
  lua_pushinteger(state, (lua_Integer)arg);
}

void lua_push(lua_State *state, bool arg) {
  lua_pushboolean(state, arg ? 1 : 0);
}

void lua_push(lua_State *state, float arg) {
  lua_pushnumber(state, (lua_Number)arg);
}

void lua_push(lua_State *state, double arg) {
  lua_pushnumber(state, (lua_Number)arg);
}

void lua_push(lua_State *state, const char *arg) { lua_pushstring(state, arg); }

void lua_push(lua_State *state, const ProtobufMessage &arg) {
  pbmsg_to_lua_table(state, arg);
}

void lua_push(lua_State *state, void *arg) {
  lua_pushlightuserdata(state, arg);
}

void lua_push(lua_State *state, int *arg) {
  lua_pushlightuserdata(state, (void *)arg);
}

void lua_push(lua_State *state, const std::string &arg) {
  lua_pushstring(state, arg.c_str());
}

void lua_push(lua_State *state, std::string &&arg) {
  lua_pushstring(state, arg.c_str());
}

template <typename K, typename V> void set_table(lua_State *l, K k, V v) {
  LuaUtil::lua_push(l, k);
  LuaUtil::lua_push(l, v);
  lua_settable(l, -3);
}

void lua_push(lua_State *state, const kratos::time::LocalDate &local_date) {
  lua_createtable(state, 0, 32);
  set_table(state, "year", local_date.year());
  set_table(state, "month", local_date.month());
  set_table(state, "day", local_date.day());
  set_table(state, "weekday", (int)local_date.weekday());
  set_table(state, "hour", local_date.hour());
  set_table(state, "minute", local_date.minute());
  set_table(state, "second", local_date.second());
  set_table(state, "elapsed_seconds", local_date.elapsed_seconds());
  set_table(state, "elapsed_days", local_date.elapsed_days());
  set_table(state, "next_weekday", (int)local_date.next_weekday());
  set_table(state, "prev_weekday", (int)local_date.prev_weekday());
  set_table(state, "days_at_week", local_date.days_at_week());
  set_table(state, "days_at_month", local_date.days_at_month());
  set_table(state, "days_at_year", local_date.days_at_year());
  set_table(state, "days_of_month", local_date.days_of_month());
  set_table(state, "days_of_year", local_date.days_of_year());
  set_table(state, "is_last_day_of_week", local_date.is_last_day_of_week());
  set_table(state, "is_last_day_of_month", local_date.is_last_day_of_month());
  set_table(state, "is_last_day_of_year", local_date.is_last_day_of_year());
}

void lua_push(lua_State *state, kratos::http::HttpCall *call) {
  lua_createtable(state, 0, 32);
  set_table(state, "host", call->get_host());
  set_table(state, "port", call->get_port());
  set_table(state, "header", call->get_headers());
  set_table(state, "content", call->get_content());
  set_table(state, "method", call->get_method());
  set_table(state, "status_code", call->get_status_code());
  set_table(state, "uri", call->get_uri());
}

bool lua_pop_result(lua_State *state, std::int16_t &arg) {
  if (!lua_isinteger(state, -1)) {
    return false;
  }
  arg = (std::int16_t)lua_tointeger(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, std::int32_t &arg) {
  if (!lua_isinteger(state, -1)) {
    return false;
  }
  arg = (std::int32_t)lua_tointeger(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, std::int64_t &arg) {
  if (!lua_isinteger(state, -1)) {
    return false;
  }
  arg = (std::int64_t)lua_tointeger(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, std::uint16_t &arg) {
  if (!lua_isinteger(state, -1)) {
    return false;
  }
  arg = (std::uint16_t)lua_tointeger(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, std::uint32_t &arg) {
  if (!lua_isinteger(state, -1)) {
    return false;
  }
  arg = (std::uint32_t)lua_tointeger(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, std::uint64_t &arg) {
  if (!lua_isinteger(state, -1)) {
    return false;
  }
  arg = (std::uint64_t)lua_tointeger(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, bool &arg) {
  if (!lua_isboolean(state, -1)) {
    return false;
  }
  arg = lua_toboolean(state, -1) ? true : false;
  return true;
}

bool lua_pop_result(lua_State *state, std::string &arg) {
  if (!lua_isstring(state, -1)) {
    return false;
  }
  arg = lua_tostring(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, float &arg) {
  if (!lua_isnumber(state, -1)) {
    return false;
  }
  arg = (float)lua_tonumber(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, double &arg) {
  if (!lua_isnumber(state, -1)) {
    return false;
  }
  arg = (double)lua_tonumber(state, -1);
  return true;
}

bool lua_pop_result(lua_State *state, ProtobufMessage &arg) {
  if (!lua_istable(state, -1)) {
    return false;
  }
  lua_table_to_pbmsg(state, arg);
  return true;
}

bool lua_pop_result(lua_State *state, kratos::http::HttpCall &call) {
  static std::string header("header");
  static std::string content("content");
  static std::string method("method");
  static std::string uri("uri");
  static std::string status_code("status_code");
  // 遍历Lua表
  lua_pushnil(state);
  while (lua_next(state, -2)) {
    if (!lua_isstring(state, -1) || !lua_isstring(state, -2)) {
      return false;
    }
    const auto *key = lua_tostring(state, -2);
    if (header == key) {
      kratos::http::HeaderMap header_map;
      on_lua_string_map(state, header_map);
      call.set_headers(header_map);
    } else {
      const auto *value = lua_tostring(state, -1);
      if (content == key) {
        call.set_content(value);
      } else if (method == key) {
        call.set_method(value);
      } else if (uri == key) {
        call.set_uri(uri);
      } else if (status_code == key) {
        try {
          call.set_status_code(std::stoi(value));
        } catch (...) {
          return false;
        }
      }
    }
    lua_pop(state, 1);
  }
  return true;
}

bool lua_pop_result(
    lua_State *state,
    kratos::service::PoolUnorederedMap<std::string, std::string> &map) {
  on_lua_string_map(state, map);
  return true;
}

bool lua_pop_result(lua_State *state, kratos::http::HeaderMap &map) {
  on_lua_string_map(state, map);
  return true;
}

bool lua_pop_result(lua_State *state, std::vector<std::string> &vec) {
  // 遍历Lua表
  lua_pushnil(state);
  while (lua_next(state, -2)) {
    if (!lua_isstring(state, -1)) {
      throw std::runtime_error("Only support string table");
    }
    auto *value = lua_tostring(state, -1);
    vec.push_back(value);
    lua_pop(state, 1);
  }
  return true;
}

void push_table_bool(lua_State *L, const ProtobufMessage &msg,
                     const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetBool(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_int32(lua_State *L, const ProtobufMessage &msg,
                      const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetInt32(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_uint32(lua_State *L, const ProtobufMessage &msg,
                       const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetUInt32(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_int64(lua_State *L, const ProtobufMessage &msg,
                      const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetInt64(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_uint64(lua_State *L, const ProtobufMessage &msg,
                       const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetUInt64(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_float(lua_State *L, const ProtobufMessage &msg,
                      const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetFloat(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_double(lua_State *L, const ProtobufMessage &msg,
                       const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetDouble(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_string(lua_State *L, const ProtobufMessage &msg,
                       const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetString(msg, field);
  lua_push(L, value.c_str());
  lua_setfield(L, -2, field->name().c_str());
}

void push_table_enum(lua_State *L, const ProtobufMessage &msg,
                     const ProtobufFieldDescriptor *field) {
  auto value = msg.GetReflection()->GetEnumValue(msg, field);
  lua_push(L, value);
  lua_setfield(L, -2, field->name().c_str());
}

void set_field_string(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isstring(L, index)) {
    reflection->SetString(&msg, field, lua_tostring(L, index));
  }
}

void set_field_int32(lua_State *L, ProtobufMessage &msg,
                     const ProtobufReflection *reflection,
                     const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->SetInt32(&msg, field, (std::int32_t)lua_tointeger(L, index));
  }
}

void set_field_int64(lua_State *L, ProtobufMessage &msg,
                     const ProtobufReflection *reflection,
                     const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->SetInt64(&msg, field, (std::int64_t)lua_tointeger(L, index));
  }
}

void set_field_uint32(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->SetUInt32(&msg, field, (std::uint32_t)lua_tointeger(L, index));
  }
}

void set_field_uint64(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->SetUInt64(&msg, field, (std::uint64_t)lua_tointeger(L, index));
  }
}

void set_field_bool(lua_State *L, ProtobufMessage &msg,
                    const ProtobufReflection *reflection,
                    const ProtobufFieldDescriptor *field, int index) {
  if (lua_isboolean(L, index)) {
    reflection->SetBool(&msg, field, lua_toboolean(L, index) ? true : false);
  }
}

void set_field_float(lua_State *L, ProtobufMessage &msg,
                     const ProtobufReflection *reflection,
                     const ProtobufFieldDescriptor *field, int index) {
  if (lua_isnumber(L, index)) {
    reflection->SetFloat(&msg, field, (float)lua_tonumber(L, index));
  }
}

void set_field_double(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isnumber(L, index)) {
    reflection->SetDouble(&msg, field, (double)lua_tonumber(L, index));
  }
}

void set_field_enum(lua_State *L, ProtobufMessage &msg,
                    const ProtobufReflection *reflection,
                    const ProtobufFieldDescriptor *field, int index) {
  if (lua_isnumber(L, index)) {
    reflection->SetEnumValue(&msg, field, (int)lua_tointeger(L, index));
  }
}

void add_field_int32(lua_State *L, ProtobufMessage &msg,
                     const ProtobufReflection *reflection,
                     const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->AddInt32(&msg, field, (std::int32_t)lua_tointeger(L, index));
  }
}

void add_field_int64(lua_State *L, ProtobufMessage &msg,
                     const ProtobufReflection *reflection,
                     const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->AddInt64(&msg, field, (std::int64_t)lua_tointeger(L, index));
  }
}

void add_field_uint32(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->AddUInt32(&msg, field, (std::uint32_t)lua_tointeger(L, index));
  }
}

void add_field_uint64(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isinteger(L, index)) {
    reflection->AddUInt64(&msg, field, (std::uint64_t)lua_tointeger(L, index));
  }
}

void add_field_string(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isstring(L, index)) {
    reflection->AddString(&msg, field, lua_tostring(L, index));
  }
}

void add_field_bool(lua_State *L, ProtobufMessage &msg,
                    const ProtobufReflection *reflection,
                    const ProtobufFieldDescriptor *field, int index) {
  if (lua_isboolean(L, index)) {
    reflection->AddBool(&msg, field, lua_toboolean(L, index) ? true : false);
  }
}

void add_field_float(lua_State *L, ProtobufMessage &msg,
                     const ProtobufReflection *reflection,
                     const ProtobufFieldDescriptor *field, int index) {
  if (lua_isnumber(L, index)) {
    reflection->AddFloat(&msg, field, (float)lua_tonumber(L, index));
  }
}

void add_field_double(lua_State *L, ProtobufMessage &msg,
                      const ProtobufReflection *reflection,
                      const ProtobufFieldDescriptor *field, int index) {
  if (lua_isnumber(L, index)) {
    reflection->AddDouble(&msg, field, (double)lua_tonumber(L, index));
  }
}

void add_field_enum(lua_State *L, ProtobufMessage &msg,
                    const ProtobufReflection *reflection,
                    const ProtobufFieldDescriptor *field, int index) {
  if (lua_isnumber(L, index)) {
    reflection->AddEnumValue(&msg, field, (int)lua_tointeger(L, index));
  }
}

bool get_lua_stack(lua_State *L, int level, std::stringstream &ss) {
  lua_Debug ar;
  if (!lua_getstack(L, level, &ar)) {
    return false;
  }
  lua_getinfo(L, "Slnu", &ar);
  // 记录调试器栈信息
  auto line = ar.currentline;
  std::string source;
  if (ar.source) {
    source = util::ltrim(ar.source, "@");
  } else {
    source = "";
  }
  auto file_name = source;
  auto func_name = ar.name ? ar.name : "";
  auto func_type = ar.namewhat ? ar.namewhat : "";
  auto func_domain = ar.what ? ar.what : "";
  ss << file_name << "(" << func_name << ":" << line << ")(" << func_type << ":"
     << func_domain << ")";
  return true;
}

std::string get_lua_traceback(lua_State *L, lua_State *thread) {
  std::stringstream ss;
  if (thread) {
    const char *error_msg = lua_tostring(thread, -1);
    luaL_traceback(L, thread, error_msg, 0);
    ss << lua_tostring(thread, -1);
  } else {
    const char *error_msg = lua_tostring(L, -1);
    luaL_traceback(L, L, error_msg, 0);
    ss << lua_tostring(L, -1);
  }
  auto *vm = (thread != nullptr) ? thread : L;
  ss << std::endl;
  int level = 0;
  while (get_lua_stack(vm, level++, ss)) {
    ss << std::endl;
  }
  return ss.str();
}

bool catch_lua_error(lua_State *L, int status, std::string &error) {
  switch (status) {
  case LUA_OK:
  case LUA_YIELD:
    return true;
  case LUA_ERRRUN:
  case LUA_ERRERR:
    error = get_lua_traceback(L, nullptr);
    break;
  default:
    break;
  }
  return false;
}

bool catch_lua_error(LuaThread *thread, int status, std::string &error) {
  switch (status) {
  case LUA_ERRRUN:
  case LUA_ERRERR:
    error = get_lua_traceback(thread->get_lua_main(), thread->get_lua_state());
    break;
  default:
    break;
  }
  return true;
}

bool register_function(lua_State *L, const char *name, LuaCFunction f) {
  lua_pushcfunction(L, *f.target<lua_CFunction>());
  lua_setglobal(L, name);
  return true;
}

ThreadState get_status(lua_State *L) {
  switch (lua_status(L)) {
  case LUA_OK:
    return ThreadState::READY;
  case LUA_YIELD:
    return ThreadState::YIELD;
  case LUA_ERRRUN:
    return ThreadState::DEAD;
  default:
    return ThreadState::DEAD;
  }
  return ThreadState::DEAD;
}

auto get_global_table(lua_State *L, const std::string &name) -> bool {
  if (!lua_getglobal(L, name.c_str())) {
    return false;
  }
  return true;
}

auto get_registry_table(lua_State *L, int *address) -> bool {
  lua_push(L, address);
  lua_rawget(L, LUA_REGISTRYINDEX);
  return true;
}

auto set_registry_table(lua_State *L) -> bool {
  lua_rawset(L, LUA_REGISTRYINDEX);
  return true;
}

auto get_registry_value(lua_State *L, const char *key) -> bool {
  lua_getfield(L, LUA_REGISTRYINDEX, key);
  return true;
}

auto new_registry_ref(lua_State *L) -> int { return luaL_ref(L, -2); }

auto remove_registry_ref(lua_State *L, int key) -> void {
  luaL_unref(L, -1, key);
}

auto new_thread(lua_State *L) -> lua_State * { return lua_newthread(L); }

auto create_table(lua_State *L) -> bool {
  lua_createtable(L, 0, 32);
  return true;
}

auto new_registry_table(lua_State *L) -> int {
  lua_newtable(L);
  return luaL_ref(L, LUA_REGISTRYINDEX);
}

auto remove_registry_key(lua_State *L, int &reg_key) -> void {
  if (reg_key != LUA_NOREF) {
    luaL_unref(L, LUA_REGISTRYINDEX, reg_key);
  }
  reg_key = LUA_NOREF;
}

LuaState::LuaState(lua_State *l) {
  L_ = l;
  top_ = lua_gettop(l);
}

LuaState::LuaState(LuaThread *thread) {
  L_ = thread->get_lua_state();
  top_ = lua_gettop(L_);
}

LuaState::LuaState(kratos::lua::ThreadPtr thread) {
  L_ = thread->get_lua_state();
  top_ = lua_gettop(L_);
}

LuaState::~LuaState() {}

bool LuaState::push_value(int index) {
  lua_pushvalue(L_, index);
  return true;
}

bool LuaState::get_reg_table(int register_key) {
  lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key);
  return true;
}

bool LuaState::get_lua_reg_function(int register_key, int key) {
  auto ret = lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key);
  if (!ret) {
    return false;
  }
  // key
  LuaUtil::lua_push(L_, key);
  // 获取lua function并压到栈顶
  lua_gettable(L_, -2);
  if (!lua_isfunction(L_, -1)) {
    return false;
  }
  // 删除注册表引用
  lua_remove(L_, -2);
  return true;
}

bool LuaState::get_lua_reg_function(int register_key, const std::string &key) {
  auto ret = lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key);
  if (!ret) {
    return false;
  }
  // key
  LuaUtil::lua_push(L_, key);
  // 获取lua function并压到栈顶
  lua_gettable(L_, -2);
  if (!lua_isfunction(L_, -1)) {
    return false;
  }
  // 删除注册表引用
  lua_remove(L_, -2);
  return true;
}

bool LuaState::add_lua_reg_function(int register_key, int key) {
  if (!lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key)) {
    return false;
  }
  // key
  LuaUtil::lua_push(L_, key);
  if (!lua_isfunction(L_, -3)) {
    lua_pop(L_, 2);
    return false;
  }
  // lua函数压入栈顶
  lua_pushvalue(L_, -3);
  // 设置到注册表
  lua_settable(L_, -3);
  // 注册表出栈
  lua_pop(L_, 1);
  return true;
}

bool LuaState::add_lua_reg_function(int register_key, const std::string &key) {
  if (!lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key)) {
    return false;
  }
  // key
  LuaUtil::lua_push(L_, key);
  if (!lua_isfunction(L_, -3)) {
    lua_pop(L_, 2);
    return false;
  }
  // lua函数压入栈顶
  lua_pushvalue(L_, -3);
  // 设置到注册表
  lua_settable(L_, -3);
  // 注册表出栈
  lua_pop(L_, 1);
  return true;
}

bool LuaState::remove_lua_reg_function(int register_key, int key) {
  if (!lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key)) {
    return false;
  }
  LuaUtil::remove_table(L_, key, -3);
  // 注册表出栈
  lua_pop(L_, 1);
  return true;
}

bool LuaState::remove_lua_reg_function(int register_key,
                                       const std::string &key) {
  if (!lua_rawgeti(L_, LUA_REGISTRYINDEX, register_key)) {
    return false;
  }
  LuaUtil::remove_table(L_, key, -3);
  // 注册表出栈
  lua_pop(L_, 1);
  return true;
}

int LuaState::push_nil() {
  lua_pushnil(L_);
  return 1;
}

bool LuaState::isstring(int index) const {
  return (lua_isstring(L_, index) != 0);
}

bool LuaState::isinteger(int index) const {
  return (lua_isinteger(L_, index) != 0);
}

bool LuaState::isnumber(int index) const {
  return (lua_isnumber(L_, index) != 0);
}

bool LuaState::isfunction(int index) const {
  return (lua_isfunction(L_, index) != 0);
}

bool LuaState::isuserdata(int index) const {
  return (lua_isuserdata(L_, index) != 0);
}

bool LuaState::istable(int index) const {
  return (lua_istable(L_, index) != 0);
}

bool LuaState::isbool(int index) const {
  return (lua_isboolean(L_, index) != 0);
}

void LuaState::pop(int count) { lua_pop(L_, count); }

bool LuaState::register_function(const char *name, LuaCFunction cfunc) {
  return LuaUtil::register_function(L_, name, cfunc);
}

void LuaState::restore_stack() { lua_settop(L_, top_); }

} // namespace LuaUtil
} // namespace lua
} // namespace kratos
